package ch.hslu.dmg;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import javax.persistence.Query;

import ch.hslu.dmg.model.Belegung;
import ch.hslu.dmg.model.Bestellung;
import ch.hslu.dmg.model.Maschine;
import ch.hslu.dmg.model.Mitarbeiter;
import ch.hslu.dmg.model.Teil;

public class OrderCalculation {

	FertigungsHelper fHelper;
	// ArrayList<UnterteilCalculation> unterteils = new
	// ArrayList<UnterteilCalculation>();
	int depth = 0;

	long orderedTeilId;
	Bestellung belongingBestellung;
	Date readyDate = addDay(new Date(), -1); // because of supplied teils that
												// are always ready.

	public OrderCalculation(long teilid, Bestellung belongingBestellung,
			FertigungsHelper fHelper) {
		orderedTeilId = teilid;
		this.belongingBestellung = belongingBestellung;
		this.fHelper = fHelper;
		// this.depth = calculateAllUnterteils();
		// only start a transaction if it's not handeld from the outside
		if (this.belongingBestellung == null) {
			fHelper.em().getTransaction().begin();
		}
		calcUnterteils(fHelper.getTeilById(orderedTeilId));
		if (belongingBestellung == null) {
			fHelper.em().getTransaction().rollback();
		}
	}

	public Date getReadyDate() {
		return addDay(readyDate, 1); // because it's ready a f t e r this day.
	}

	/*
	 * private int calculateAllUnterteils() {
	 * System.out.println("Calculating the 'Unterteile' for Teil with id " +
	 * orderedTeilId); String queryString =
	 * "with th (oberteil_id, unterteil_id, depth) as\n" +
	 * "(select oberteil_id,unterteil_id, (select 0) as depth from\n" +
	 * "     BestehtAus where oberteil_id=" + orderedTeilId + "\n" +
	 * "union all\n" +
	 * "select b.oberteil_id,b.unterteil_id, (th.depth + 1) from\n" +
	 * "BestehtAus b join th on th.unterteil_id=b.oberteil_id)\n" + "\n" +
	 * "select distinct oberteil_id, unterteil_id, depth from th;"; Query query
	 * = fg.em().createNativeQuery(queryString); List r = query.getResultList();
	 * 
	 * int deepest = 0; for (Object o : r) { Object[] oArray = (Object[]) o;
	 * System.out .println("  Found 'Unterteil' with id " + (long) oArray[1] +
	 * " (depth: " + (int) oArray[2] + "). It belongs to Teil with id " + (long)
	 * oArray[0] + "."); unterteils.add(new UnterteilCalculation((long)
	 * oArray[1], (long) oArray[0], (int) oArray[2])); deepest = (int)
	 * oArray[2]; }
	 * 
	 * return deepest; }
	 */

	private Date calcUnterteils(Teil t) {
		printSpaces();
		System.out.println("Calculating " + t.getBezeichnung() + " ("
				+ t.getTeilid() + ").");
		depth += 2;
		Date earliestProducePossibility = addDay(new Date(), -1); // because of
																	// supplied
																	// teils
																	// that are
																	// always
																	// ready.
		for (Teil ut : t.getTeilList()) {
			Date puttedAt = calcUnterteils(ut);
			if (puttedAt.after(earliestProducePossibility)) {
				earliestProducePossibility = puttedAt;
			}
		}
		Date putted = putBelegung(t, earliestProducePossibility);
		printSpaces();
		SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy");
		System.out.println(t.getBezeichnung() + " (" + t.getTeilid()
				+ ") will be ready after " + dateFormat.format(putted) + ".");
		updateReadyDate(putted);
		depth -= 2;
		return putted;
	}

	private Date putBelegung(Teil ut, Date earliestProducePossibility) {
		printSpaces();
		System.out.println("Calculating when " + ut.toString() + " ("
				+ ut.getTeilid() + ") can be produced.");
		// Supplied by Lieferant?
		if (ut.getLieferantid() != null) {
			printSpaces();
			System.out
					.println("-Teil " + ut.toString() + " (" + ut.getTeilid()
							+ ") is supplied by "
							+ ut.getLieferantid().getName() + ".");
			return addDay(new Date(), -1); // because it's ready already at the
											// beggining of the day.
		}

		// first get the machine that is needed
		earliestProducePossibility = addDay(earliestProducePossibility, 1);
		Maschine m = ut.getFertigungid().getMachineid();
		printSpaces();
		System.out.println("-To produce " + ut.toString() + " the machine "
				+ m.getBezeichnung() + " is needed.");
		while (!isMachineFree(m, earliestProducePossibility)) {
			printSpaces();
			System.out
					.println("(-Machine not ready on the "
							+ getFormattedDate(earliestProducePossibility,
									"yyyy-MM-dd") + ".)");
			earliestProducePossibility = addDay(earliestProducePossibility, 1);
		}
		// second get all possible employees
		List<Mitarbeiter> mitarbeiterList = m.getMitarbeiterList();
		printSpaces();
		System.out.println("-The machine " + m.getBezeichnung()
				+ " can be controlled by " + mitarbeiterList.size()
				+ " employes.");

		// now find the first possible Belegung and insert it
		while (true) { // do as long as no date can be found
			for (Mitarbeiter mitarbeiter : mitarbeiterList) {
				if (isMitarbeiterWithMachineFree(mitarbeiter, m,
						earliestProducePossibility)) {
					Belegung b = new Belegung();
					b.setDatum(getFormattedDate(earliestProducePossibility,
							"yyyy-MM-dd"));
					b.setBestellungid(belongingBestellung);
					b.setMachineid(m);
					b.setMitarbeiterid(mitarbeiter);
					fHelper.em().persist(b);
					printSpaces();
					System.out.println("(Adding Belegung: "
							+ m.getBezeichnung() + " " + mitarbeiter.getName()
							+ " "
							+ getFormattedDate(earliestProducePossibility)
							+ ")");
					return earliestProducePossibility;
				}
			}
			earliestProducePossibility = addDay(earliestProducePossibility, 1);
		}
	}

	private void printSpaces() {
		for (int i = 0; i < depth; i++) {
			System.out.print(" ");
		}
	}

	private void updateReadyDate(Date newDate) {
		if (newDate.after(this.readyDate))
			readyDate = newDate;
	}

	private Date addDay(Date current, int amountOfDays) {
		Calendar c1 = Calendar.getInstance();
		c1.setTime(current);
		// System.out.println("Date is : " + sdf.format(c1.getTime()));
		c1.add(Calendar.DATE, amountOfDays); // or Calendar.DAY_OF_MONTH which
												// is a synonym
		// System.out.println("New Date is : " + sdf.format(c1.getTime()));
		return c1.getTime();
	}

	private boolean isMachineFree(Maschine maschine, Date dateToCheck) {
		Query query = fHelper
				.em()
				.createQuery(
						"SELECT b FROM Belegung b WHERE b.datum = :date AND b.machineid = :maschine");
		query.setParameter("date", getFormattedDate(dateToCheck, "yyyy-MM-dd"));
		query.setParameter("maschine", maschine);
		return query.getResultList().size() > 0 ? false : true;
	}

	private boolean isMitarbeiterWithMachineFree(Mitarbeiter mitarbeiter,
			Maschine maschine, Date dateToCheck) {
		Query query = fHelper
				.em()
				.createQuery(
						"SELECT b FROM Belegung b WHERE b.mitarbeiterid = :mitarbeiter AND b.datum = :date AND b.machineid = :maschine");
		query.setParameter("mitarbeiter", mitarbeiter);
		query.setParameter("date", getFormattedDate(dateToCheck, "yyyy-MM-dd"));
		query.setParameter("maschine", maschine);
		return query.getResultList().size() > 0 ? false : true;
	}

	private String getFormattedDate(Date d) {
		return getFormattedDate(d, "dd-MM-yyyy");
	}

	private String getFormattedDate(Date d, String format) {
		SimpleDateFormat dateFormat = new SimpleDateFormat(format);
		return dateFormat.format(d);
	}

}
